package dao

import (
	"database/sql"
	"fmt"
	"reflect"
	"strings"
	"time"

	"hong/uniapp_server/common/utils"
	"hong/uniapp_server/resources"

	"github.com/go-sql-driver/mysql"
)

const NoRowsError = "sql: no rows in result set"

var db *sql.DB

func InitMySql(mysqlConfig resources.MysqlConfig) error {
	cfg := mysql.Config{
		User:                 mysqlConfig.User,
		Passwd:               mysqlConfig.Password,
		Net:                  "tcp",
		Addr:                 fmt.Sprintf("%v:%v", mysqlConfig.Host, mysqlConfig.Port),
		DBName:               mysqlConfig.Database,
		ParseTime:            true,
		AllowNativePasswords: true,
	}
	var err error
	db, err = sql.Open("mysql", cfg.FormatDSN())

	if err != nil {
		return err
	}
	db.SetMaxOpenConns(mysqlConfig.MaxOpenConns)
	db.SetMaxIdleConns(mysqlConfig.MaxIdleConns)
	db.SetConnMaxLifetime(time.Duration(mysqlConfig.MaxLifeTime) * time.Second)
	db.SetConnMaxIdleTime(time.Duration(mysqlConfig.MaxIdleTime) * time.Second)
	return nil
}

func GetMySql() *sql.DB {
	return db
}

func Get[T any](sql string, args ...any) (*T, error) {
	var t T
	row := db.QueryRow(sql, args...)
	if err := mapRowToStruct(row, &t); err != nil {
		return nil, err
	}
	return &t, nil
}
func GetSlice[T any](sql string, args ...any) ([]*T, error) {
	var ts []*T
	rows, err := db.Query(sql, args...)
	if err != nil {
		return nil, err
	}
	defer rows.Close()
	for rows.Next() {
		var t T
		if err := mapRowsToStruct(rows, &t); err != nil {
			return nil, err
		}
		ts = append(ts, &t)
	}
	return ts, nil
}

// Example usage:
//
//	type User struct {
//	  ID        int
//	  FirstName string
//	  LastName  string
//	}
//	fields := generateSqlFields(User{})
//	// fields will be "id, first_name, last_name"
func generateSqlFields(i interface{}) string {
	t := reflect.TypeOf(i)
	if t.Kind() == reflect.Ptr {
		t = t.Elem()
	}

	if t.Kind() != reflect.Struct {
		return ""
	}

	var fields []string
	for i := 0; i < t.NumField(); i++ {
		field := t.Field(i)
		// Check if the field has a "db" tag
		if dbTag, ok := field.Tag.Lookup("db"); ok {
			// If the "db" tag is present, use its value instead of the field name
			// ignore "-" tag
			if dbTag != "-" {
				fields = append(fields, dbTag)
			}
		} else {
			fieldName := utils.CamelToSnake(field.Name)
			fields = append(fields, fieldName)
		}
	}

	return strings.Join(fields, ", ")
}

func mapRowsToStruct(rows *sql.Rows, s interface{}) error {
	fields := reflectFields(reflect.ValueOf(s).Elem())
	return rows.Scan(fields...)
}

func mapRowToStruct(row *sql.Row, s interface{}) error {
	fields := reflectFields(reflect.ValueOf(s).Elem())
	return row.Scan(fields...)
}

func reflectFields(v reflect.Value) []interface{} {
	fields := []interface{}{}
	for i := 0; i < v.NumField(); i++ {
		if dbTag, ok := v.Type().Field(i).Tag.Lookup("db"); ok {
			// ignore "-" tag
			if dbTag == "-" {
				continue
			}
		}
		fields = append(fields, v.Field(i).Addr().Interface())
	}
	return fields
}
